/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.worker;

import genomemap.data.DataSource;
import genomemap.data.PDataSource;

import javautil.collections.ArrayUtil;

/**
 *
 * @author stewari1
 */
public class HammingDistance implements IncrementallyAnnealable {

    /**
     * HammingDistance used for ordering probes
     */
    private static final int ORDER_PROBES = 0;

    /**
     * HammingDistance used for ordering clones
     */
    public static final int ORDER_CLONES = 1;

    /** Field description */
    private boolean dataLoaded = false;

    /** Field description */
    private int linkageGroup = -1;

    /** Field description */
    private int[] order = null;

    /** Field description */
    private int orderType = 0;

    /** Field description */
    private int[][] linkDist = null;

    /**
     *
     * @param linkageGroup
     * @param orderType the type of ordering
     */
    public HammingDistance(int linkageGroup, int orderType) {

        this.linkageGroup = linkageGroup;
        this.orderType    = orderType;
    }

    void loadData() {

        PDataSource pData = DataSource.getPDataSource(linkageGroup);

        loadData(pData.getHybridData());
    }

    void loadData(int[][] hybridData) {

        /* calculate the linking distance matrix */
        int nc = 0,
            np = 0;

        switch (orderType) {

            case ORDER_PROBES :


                // shuffle initial clone order
                int k, l;
                int[] hb = new int[hybridData[0].length];

                for (int cloneIndex = 0; cloneIndex < hybridData.length; cloneIndex++) {

                    k = (int) (hybridData.length * Math.random());
                    l = (int) (hybridData.length * Math.random());

                    for (int probeIndex = 0; probeIndex < hybridData[0].length; probeIndex++) {

                        hb[probeIndex] = hybridData[k][probeIndex];
                    }

                    for (int probeIndex = 0; probeIndex < hybridData[0].length; probeIndex++) {

                        hybridData[k][probeIndex] = hybridData[l][probeIndex];
                    }

                    for (int probeIndex = 0; probeIndex < hybridData[0].length; probeIndex++) {

                        hybridData[l][probeIndex] = hb[probeIndex];
                    }
                }

                int[][] HBP = ArrayUtil.transpose(hybridData);

                nc            = HBP[0].length;
                np            = HBP.length;
                this.linkDist = new int[np][np];

                for (int i = 0; i < np; i++) {

                    for (int j = 0; j < np; j++) {

                        int n = 0;

                        for (int m = 0; m < nc; m++) {

                            if (HBP[i][m] != HBP[j][m]) {
                                n++;
                            }
                        }

                        linkDist[i][j] = n;
                    }
                }

                break;

            case ORDER_CLONES :


                // shuffle initial clone order
                for (int cloneIndex = 0; cloneIndex < hybridData.length; cloneIndex++) {

                    k = (int) (hybridData.length * Math.random());
                    l = (int) (hybridData.length * Math.random());

                    int val = order[k];

                    order[k] = order[l];
                    order[l] = val;
                }

                nc            = hybridData.length;
                np            = hybridData[0].length;
                this.linkDist = new int[nc][nc];

                for (int i = 0; i < nc; i++) {

                    for (int j = 0; j < nc; j++) {

                        int n = 0;

                        for (int m = 0; m < np; m++) {

                            if (hybridData[order[i] - 1][m] != hybridData[order[j] - 1][m]) {
                                n++;
                            }
                        }

                        linkDist[order[i] - 1][order[j] - 1] = n;
                    }
                }

                break;
        }

        dataLoaded = true;
    }

    /**
     * difference in value for the block reversal enpoints k and l
     * @param k
     * @param l
     * @return
     */
    @Override
    public double delta(int k, int l) {

        double dd       = 0.0;
        int orderLength = order.length;

        /* calculate linking distance difference due to reversing */
        if ((k > 0) && (l < orderLength - 1)) {

            dd = linkDist[order[k - 1] - 1][order[l] - 1]
                 - linkDist[order[k - 1] - 1][order[k] - 1]
                 + linkDist[order[k] - 1][order[l + 1] - 1]
                 - linkDist[order[l] - 1][order[l + 1] - 1];
        }

        if ((k == 0) && (l < orderLength - 1)) {

            dd = linkDist[order[k] - 1][order[l + 1] - 1]
                 - linkDist[order[l] - 1][order[l + 1] - 1];
        }

        if ((k > 0) && (l == orderLength - 1)) {

            dd = linkDist[order[k - 1] - 1][order[l] - 1]
                 - linkDist[order[k - 1] - 1][order[k] - 1];
        }

        return dd;
    }

    @Override
    public double compute() {

        /* order to single linking distance */
        double ld = 0.0;

        for (int i = 0; i < order.length - 1; i++) {

            ld += linkDist[order[i] - 1][order[i + 1] - 1];
        }

        return ld;
    }

    @Override
    public double initialValue() {

        if (!dataLoaded) {
            loadData();
        }

        return compute();
    }

    @Override
    public int[] getOrder() {
        return order;
    }

    @Override
    public void setOrder(int[] order) {
        this.order = order;
    }
}
